/*******************************************************************************
 * Package: com.song.flowable.service.impl
 * Type:    FlowTaskServiceImpl
 * Date:    2023-11-28 22:55
 *
 * Copyright (c) 2023 LTD All Rights Reserved.
 *
 * You may not use this file except in compliance with the License.
 *******************************************************************************/
package com.song.flowable.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.song.common.util.CommUtil;
import com.song.flowable.convert.TaskConvert;
import com.song.flowable.dto.*;
import com.song.flowable.service.FlowTaskService;
import com.song.flowable.util.FlowUtil;
import com.song.flowable.vo.DoneVO;
import com.song.flowable.vo.ReturnTaskListVO;
import com.song.flowable.vo.ReturnTaskVo;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.util.ExecutionGraphUtil;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.idm.api.Group;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 功能描述： 流程任务
 *
 * @author Songxianyang
 * @date 2023-11-28 22:55
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class FlowTaskServiceImpl implements FlowTaskService {

    private final TaskService taskService;

    private final RepositoryService repositoryService;

    private final HistoryService historyService;


    private final IdentityService identityService;

    private final RuntimeService runtimeService;

    private final ProcessEngine processEngine;


    /**
     * 更新任务的负责人(转办或者转派负责人)
     *
     * @param taskId
     * @param targetUserId
     * @return
     */
    @Override
    public Boolean updateTaskAssignee(String taskId, String targetUserId) {
        taskService.setAssignee(taskId, targetUserId);
        return true;
    }

    /**
     * 查询可退回的节点列表
     * https://blog.csdn.net/suprezheng/article/details/122627461
     *
     * @param taskId
     * @return
     */
    @Override
    public List<ReturnTaskListVO> getReturnTaskList(String taskId) {
        // 初始化返回结果列表
        List<ReturnTaskListVO> result = new ArrayList<>(16);
        if (StringUtils.isBlank(taskId)) {
            return result;
        }
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return result;
        }
        // 任务定义key 等于 当前任务节点id
        String taskDefinitionKey = task.getTaskDefinitionKey();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        Process mainProcess = bpmnModel.getMainProcess();
        // 当前节点
        FlowNode currentFlowElement = (FlowNode) mainProcess.getFlowElement(taskDefinitionKey, true);
        // 查询历史节点实例
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(task.getProcessInstanceId())
                .finished()
                .orderByHistoricActivityInstanceEndTime().asc().list();
        List<String> activityIdList = historicActivityInstances.stream()
                // || BpmnXMLConstants.ELEMENT_EVENT_START.equals(activityInstance.getActivityType()) 退回的流程节点不包括开始节点 ：ELEMENT_EVENT_START = "startEvent"
                .filter(activityInstance ->
                        BpmnXMLConstants.ELEMENT_TASK_USER.equals(activityInstance.getActivityType()))
                .map(HistoricActivityInstance::getActivityId)
                .filter(activityId -> !taskDefinitionKey.equals(activityId))
                .distinct()
                .collect(Collectors.toList());
        for (String activityId : activityIdList) {
            // 回退到主流程的节点
            FlowNode toBackFlowElement = (FlowNode) mainProcess.getFlowElement(activityId, true);
            // 判断 【工具类判断是否可以从源节点 到 目标节点】
            Set<String> set = new HashSet<>();
            if (toBackFlowElement != null && ExecutionGraphUtil.isReachable(mainProcess, toBackFlowElement, currentFlowElement, set)) {
                ReturnTaskListVO workFlowNodeDTO = new ReturnTaskListVO();
                workFlowNodeDTO.setNodeId(activityId);
                workFlowNodeDTO.setNodeName(toBackFlowElement.getName());
                result.add(workFlowNodeDTO);
            }
        }
        return result;
    }

    /**
     * 委派
     *
     * @param taskId
     * @param assignUserId 要委派的用户
     * @return
     */
    @Override
    public Boolean assign(String taskId, String assignUserId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        Assert.notNull(task, "未查到当前任务节点");
        if (task != null) {
            taskService.delegateTask(task.getId(), assignUserId);
        }
        return true;
    }

    /**
     * 拾取任务
     *
     * @param taskId
     * @param claimUserId
     * @return
     */
    @Override
    public Boolean claim(String taskId, String claimUserId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        Assert.notNull(task, "未查到当前任务节点");
        // 拾取任务
        taskService.claim(task.getId(), claimUserId);
        return Boolean.TRUE;
    }


    /**
     * 任务归还
     *
     * @param taskId
     * @return
     */
    @Override
    public Boolean unClaim(String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        Assert.notNull(task, "未查到当前任务节点");
        taskService.unclaim(task.getId());
        return Boolean.TRUE;
    }

    /**
     * 如果任务拾取后，不想操作也不想归还，可以交接给他人进行处理
     *
     * @param taskId
     * @param handoverUserId
     * @return
     */
    @Override
    public Boolean handover(String taskId, String handoverUserId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        Assert.notNull(task, "未查到当前任务节点");
        taskService.setAssignee(task.getId(), handoverUserId);
        return Boolean.TRUE;
    }

    /**
     * 单独完成任务
     *
     * @param dto
     * @return
     */
    @Override
    public Boolean complete(CompleteDTO dto) {
        Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult();
        Assert.notNull(task, "未查到当前任务节点");
        taskService.addComment(task.getId(), dto.getProcessInstanceId(), dto.getMessage());
        // 委派用户走这个分支
        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            taskService.resolveTask(task.getId());
            return Boolean.TRUE;
        }
        // 完成
        taskService.complete(task.getId(), dto.getMap());
        return Boolean.TRUE;
    }

    /**
     * 已办数据
     *
     * @param dto
     * @return
     */
    @Override
    public Page<DoneVO> doneList(DoneDTO dto) {
        Page<DoneVO> page = new Page<>();
        HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                .includeProcessVariables()
                .finished()
                .taskAssignee(dto.getUserId())
                .orderByHistoricTaskInstanceEndTime()
                .desc();

        List<HistoricTaskInstance> historicTaskInstanceList = taskInstanceQuery.listPage(dto.getPageSize() * (dto.getPage() - 1), dto.getPageSize());
        List<DoneVO> vos = new ArrayList<>();
        for (HistoricTaskInstance histTask : historicTaskInstanceList) {
            DoneVO vo = new DoneVO();
            // 当前流程信息
            vo.setTaskId(histTask.getId());
            // 审批人员信息
            vo.setCreateTime(histTask.getCreateTime());
            vo.setFinishTime(histTask.getEndTime());
            vo.setDuration(FlowUtil.getDate(histTask.getDurationInMillis()));
            vo.setProcDefId(histTask.getProcessDefinitionId());
            vo.setTaskDefKey(histTask.getTaskDefinitionKey());
            vo.setTaskName(histTask.getName());

            // 流程定义信息
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(histTask.getProcessDefinitionId())
                    .singleResult();
            vo.setDeployId(pd.getDeploymentId());
            vo.setProcDefName(pd.getName());
            vo.setProcDefVersion(pd.getVersion());
            vo.setProcInsId(histTask.getProcessInstanceId());
            vo.setHisProcInsId(histTask.getProcessInstanceId());
            vo.setComment(getComment(vo.getTaskId()));
            // 流程发起人信息
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(histTask.getProcessInstanceId())
                    .singleResult();
            // todo 流程发起人信息
            //Systemuser user = sysUserService.get(historicProcessInstance.getStartUserId());
            //flowTask.setStartUserName(user.getNickName());
            //flowTask.setStartDeptName(user.getDept().getDeptName());
            vo.setStartUserId(historicProcessInstance.getStartUserId());

            vos.add(vo);
        }
        page.setTotal(taskInstanceQuery.count());
        page.setRecords(vos);
        return page;
    }

    /**
     * 用户待办
     *
     * @param dto
     * @return
     */
    @Override
    public Page<ReturnTaskVo> todo(TodoDTO dto) {

        Page<ReturnTaskVo> page = new Page<>();
        // todo 用户组 ：需要适配当前自己的系统 用户组的概念  （例如某个部门的用户）
        // 这里以 flowable 用户组来整。
        List<Group> groups = identityService.createGroupQuery().groupMember(dto.getUserId()).list();

        TaskQuery taskQuery = taskService.createTaskQuery().or()
                .taskAssignee(dto.getUserId())
                .taskCandidateUser(dto.getUserId())
                .orderByTaskCreateTime().desc();
        if (CommUtil.isNotEmpty(groups)) {
            // 用户拥有多个组，根据groupIdList
            Set<String> groupIds = groups.stream().map(Group::getId).collect(Collectors.toSet());
            taskQuery.taskCandidateGroupIn(groupIds);
        }
        page.setTotal(taskQuery.count());

        List<Task> tasks = new ArrayList<>();
        if (CommUtil.isNotEmpty(dto.getPageSize()) && CommUtil.isNotEmpty(dto.getPage())) {
            tasks = taskQuery.listPage(dto.getPageSize() * (dto.getPage() - 1), dto.getPageSize());
        } else {
            tasks = taskQuery.list();
        }

        if (CommUtil.isEmpty(tasks)) {
            page.setRecords(Collections.EMPTY_LIST);
            return page;
        }
        List<ReturnTaskVo> returnTaskVos = TaskConvert.INSTANCE.toConvertTaskVoList(tasks);
        // todo 这边还需要查询一些自己系统的业务数据....
        page.setRecords(returnTaskVos);
        return page;
    }

    /**
     * 拾取任务并完成
     *
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public String accept(AcceptDTO dto) {
        Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult();
        //领取任务
        taskService.claim(task.getId(), dto.getUserId());
        // TODO 一般企业级会签开发基本上都是在前台送用户过来（然而这个用户都是通过角色去查询得到得）拿到代码根据自己的实际优化此接口。SteveCode只是想做通用才这么做的
        //会签时 入参String转List
        Map<String, Object> map = dto.getMap();
        if (Objects.nonNull(map)) {
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String value = (String) entry.getValue();
                String[] values = value.split(",");
                if (values.length > 1) {
                    entry.setValue(Arrays.asList(values));
                }
            }
        }
        // 填写审批信息(通过、拒绝）
        /*
        {
    "map":{
        "startUserId":"admin"
    },
    "message":"这是审批的消息信息",
    "processInstanceId":"9e45d4a8b59b42f2af5cd8e2b518f281",
    "taskId":"44248f5a18424d3781febde0a4cb63de",
    "userId":"admin"
}
         */
        taskService.addComment(task.getId(), dto.getProcessInstanceId(), dto.getMessage());
        // 委派用户走这个分支
        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            taskService.resolveTask(task.getId());
            return "流程执行成功！";
        }

        // 完成
        taskService.complete(task.getId(), dto.getMap());
        return "流程执行成功！";
    }


    @Override
    @Transactional
    public String reject(RejectDTO dto) {
        if (taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult().isSuspended()) {
            throw new RuntimeException("任务处于挂起状态!");
        }
        // 当前任务 task
        Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult();
        // 获取流程定义信息
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
        // 获取所有节点信息
        Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0);
        // 获取全部节点列表，包含子节点
        Collection<FlowElement> allElements = FlowUtil.getAllElements(process.getFlowElements(), null);
        // 获取当前任务节点元素
        FlowElement source = null;
        if (allElements != null) {
            for (FlowElement flowElement : allElements) {
                // 类型为用户节点
                if (flowElement.getId().equals(task.getTaskDefinitionKey())) {
                    // 获取节点信息
                    source = flowElement;
                }
            }
        }

        // 目的获取所有跳转到的节点 targetIds
        // 获取当前节点的所有父级用户任务节点
        // 深度优先算法思想：延边迭代深入
        List<UserTask> parentUserTaskList = FlowUtil.iteratorFindParentUserTasks(source, null, null);
        if (parentUserTaskList == null || parentUserTaskList.size() == 0) {
            throw new RuntimeException("当前节点为初始任务节点，不能驳回");
        }
        // 获取活动 ID 即节点 Key
        List<String> parentUserTaskKeyList = new ArrayList<>();
        parentUserTaskList.forEach(item -> parentUserTaskKeyList.add(item.getId()));
        // 获取全部历史节点活动实例，即已经走过的节点历史，数据采用开始时间升序
        List<HistoricTaskInstance> historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery().processInstanceId(task.getProcessInstanceId()).orderByHistoricTaskInstanceStartTime().asc().list();
        // 数据清洗，将回滚导致的脏数据清洗掉
        List<String> lastHistoricTaskInstanceList = FlowUtil.historicTaskInstanceClean(allElements, historicTaskInstanceList);
        // 此时历史任务实例为倒序，获取最后走的节点
        List<String> targetIds = new ArrayList<>();
        // 循环结束标识，遇到当前目标节点的次数
        int number = 0;
        StringBuilder parentHistoricTaskKey = new StringBuilder();
        for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) {
            // 当会签时候会出现特殊的，连续都是同一个节点历史数据的情况，这种时候跳过
            if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) {
                continue;
            }
            parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey);
            if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) {
                number++;
            }
            // 在数据清洗后，历史节点就是唯一一条从起始到当前节点的历史记录，理论上每个点只会出现一次
            // 在流程中如果出现循环，那么每次循环中间的点也只会出现一次，再出现就是下次循环
            // number == 1，第一次遇到当前节点
            // number == 2，第二次遇到，代表最后一次的循环范围
            if (number == 2) {
                break;
            }
            // 如果当前历史节点，属于父级的节点，说明最后一次经过了这个点，需要退回这个点
            if (parentUserTaskKeyList.contains(historicTaskInstanceKey)) {
                targetIds.add(historicTaskInstanceKey);
            }
        }
        // 目的获取所有需要被跳转的节点 currentIds
        // 取其中一个父级任务，因为后续要么存在公共网关，要么就是串行公共线路
        UserTask oneUserTask = parentUserTaskList.get(0);
        // 获取所有正常进行的任务节点 Key，这些任务不能直接使用，需要找出其中需要撤回的任务
        List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
        List<String> runTaskKeyList = new ArrayList<>();
        runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey()));
        // 需驳回任务列表
        List<String> currentIds = new ArrayList<>();
        // 通过父级网关的出口连线，结合 runTaskList 比对，获取需要撤回的任务
        List<UserTask> currentUserTaskList = FlowUtil.iteratorFindChildUserTasks(oneUserTask, runTaskKeyList, null, null);
        currentUserTaskList.forEach(item -> currentIds.add(item.getId()));
        // 规定：并行网关之前节点必须需存在唯一用户任务节点，如果出现多个任务节点，则并行网关节点默认为结束节点，原因为不考虑多对多情况
        if (targetIds.size() > 1 && currentIds.size() > 1) {
            throw new RuntimeException("任务出现多对多情况，无法撤回");
        }

        // 循环获取那些需要被撤回的节点的ID，用来设置驳回原因
        List<String> currentTaskIds = new ArrayList<>();
        currentIds.forEach(currentId -> runTaskList.forEach(runTask -> {
            if (currentId.equals(runTask.getTaskDefinitionKey())) {
                currentTaskIds.add(runTask.getId());
            }
        }));
        // 设置驳回意见 3驳回
        currentTaskIds.forEach(item -> taskService.addComment(item, task.getProcessInstanceId(), dto.getComment()));

        try {
            // 如果父级任务多于 1 个，说明当前节点不是并行节点，原因为不考虑多对多情况
            if (targetIds.size() > 1) {
                // 1 对 多任务跳转，currentIds 当前节点(1)，targetIds 跳转到的节点(多)
                runtimeService.createChangeActivityStateBuilder()
                        .processInstanceId(task.getProcessInstanceId()).
                        moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds).changeState();
            }
            // 如果父级任务只有一个，因此当前任务可能为网关中的任务
            if (targetIds.size() == 1) {
                // 1 对 1 或 多 对 1 情况，currentIds 当前要跳转的节点列表(1或多)，targetIds.get(0) 跳转到的节点(1)
                runtimeService.createChangeActivityStateBuilder()
                        .processInstanceId(task.getProcessInstanceId())
                        .moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)).changeState();
                //Map<String,Object> params = new HashedMap();
                //params.put("node3","");
                //taskService.setVariables(task.getId(),params);
            }
        } catch (FlowableObjectNotFoundException e) {
            log.error("未找到流程实例，流程可能已发生变化{}", e);
            throw new RuntimeException("未找到流程实例，流程可能已发生变化");
        } catch (FlowableException e) {
            log.error("无法取消或开始活动{}", e);
            throw new RuntimeException("无法取消或开始活动");
        }
        return "拒绝成功！！！";
    }


    /**
     * 多实例加签
     * 多实例加签
     * activityId – 当前会签流程节点id
     * parentExecutionId – 可以是流程实例 ID，
     * executionVariables – 在新创建的多实例执行中设置为局部变量的变量，并设置加签用户
     *
     * @param addMultiInstance
     * @return
     */
    @Override
    public String addMultiInstanceExecution(AddMultiInstanceDTO addMultiInstance) {
        if (Objects.isNull(addMultiInstance)) {
            return "入参对象不能为空！";
        }
        // 支持一次加多个人
        List<String> addLabelUserIds = addMultiInstance.getAddLabelUserId();
        addLabelUserIds.forEach(user -> {
            Map<String, Object> executionVariables = new HashMap<>(1);
            executionVariables.put("assignee", user);
            runtimeService.addMultiInstanceExecution(addMultiInstance.getActivityId(), addMultiInstance.getProcessInstanceId(), executionVariables);
        });
        return "加签成功，请如下用户：" + addMultiInstance.getAddLabelUserId() + "先进行审批";
    }

    /**
     * 多实例减签
     *
     * @param executionId
     * @return
     */
    @Override
    public String deleteMultiInstanceExecution(String executionId) {
        runtimeService.deleteMultiInstanceExecution(executionId, true);
        return "减签成功";
    }

    /**
     * 查询会签节点的审批人任务
     *
     * @param dto
     * @return
     */
    @Override
    public List<ReturnTaskVo> signTaskUsers(SignTaskUserDTO dto) {
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(dto.getProcessInstanceId())
                .taskName(dto.getNodeName()).list();
        if (CollectionUtils.isEmpty(tasks)) {
            return Collections.EMPTY_LIST;
        }

        List<ReturnTaskVo> returnTaskVos = TaskConvert.INSTANCE.toConvertTaskVoList(tasks);

        return returnTaskVos;
    }

    /**
     * 退回
     *
     * @param dto
     * @return
     */
    @Override
    public String flowReturn(FlowReturnDTO dto) {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 当前的任务定义key，即ACT_RU_TASK表的TASK_DEF_KEY_字段
        List<String> currentActivityIds = new ArrayList<>();
        currentActivityIds.add(dto.getCurrentNodeTaskId());
        // 需要回退的目标节点的任务定义key，即ACT_RU_TASK表的TASK_DEF_KEY_字段
        String newActivityId = dto.getTargetNodeTaskId();
        // 填写退回原因
        taskService.addComment(dto.getTaskId(), dto.getProcessInstanceId(), dto.getMessage());
        // 回退操作
        runtimeService.createChangeActivityStateBuilder()
                // 流程实例id
                .processInstanceId(dto.getProcessInstanceId())
                .moveActivityIdsToSingleActivityId(currentActivityIds, newActivityId)
                .changeState();
        return "流程退回成功！";
    }

    /**
     * 流程图
     *
     * @param response
     * @param processId
     */
    @SneakyThrows
    @Override
    public void getFlowChart(HttpServletResponse response, String processId) {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();

        //流程走完的不显示图
        if (pi == null) {
            return;
        }
        //使用流程实例ID，查询正在执行的执行对象表，返回流程实例对象
        String InstanceId = pi.getProcessInstanceId();
        List<Execution> executions = runtimeService
                .createExecutionQuery()
                .processInstanceId(InstanceId)
                .list();

        //得到正在执行的Activity的Id
        List<String> activityIds = new ArrayList<>();
        List<String> flows = new ArrayList<>();
        for (Execution exe : executions) {
            List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
            activityIds.addAll(ids);
        }
        //获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
        ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
        ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
        InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows,
                engconf.getActivityFontName(), engconf.getLabelFontName(),
                engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, true);
        // 修复流程图乱码问题
        //禁止图像缓存。
        response.setContentType("image/png");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        OutputStream out = null;
        byte[] buf = new byte[1024];
        int legth = 0;
        try {
            out = response.getOutputStream();
            while ((legth = in.read(buf)) != -1) {
                out.write(buf, 0, legth);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }

    /**
     * 获取指定用户组流程任务列表
     *
     * @param group
     * @return
     */
    @Override
    public List<Task> userGroupTasks(String group) {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup(group).list();
        return tasks;
    }

    /**
     * 驳回流程
     *
     * @param taskId
     * @param targetTaskKey
     * @return
     */
    @Override
    public String disallowTask(String taskId, String targetTaskKey) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return "节点不存在";
        }
        List<String> key = new ArrayList<>();
        key.add(task.getTaskDefinitionKey());
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(task.getProcessInstanceId())
                .moveActivityIdsToSingleActivityId(key, targetTaskKey)
                .changeState();
        return "驳回成功...";
    }

    /**
     * 获取当前任务的流程变量
     *
     * @param taskId
     * @return
     */
    @Override
    public Map<String, Object> getTaskVar(String taskId) {
        HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().includeProcessVariables().finished().taskId(taskId).singleResult();
        if (Objects.nonNull(historicTaskInstance)) {
            return historicTaskInstance.getProcessVariables();
        } else {
            Map<String, Object> var = taskService.getVariables(taskId);
            return var;
        }
    }



    private String getComment(String taskId) {
        List<Comment> taskComments = taskService.getTaskComments(taskId);
        if (CommUtil.isEmpty(taskComments)) {
            return null;
        }
        StringBuilder message = new StringBuilder();
        for (Comment taskComment : taskComments) {
            message.append(taskComment.getFullMessage());
        }
        return message.toString();
    }

}
